package base_init

import (
	"fmt"
	"github.com/hashicorp/consul/api"
	"go-likaladi-shop/common/base_config"
	"go-likaladi-shop/common/base_util"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/health"
	"google.golang.org/grpc/health/grpc_health_v1"
	"net"
	"os"
	"os/signal"
	"syscall"
)

// GrpcServerConnect 启动 gRPC 服务器（不注册到 Consul）
// 适用于本地开发或不需要服务注册的场景
//
// 参数：
// - host: 监听的 IP 地址
// - port: 监听的端口号
// - registerGrpcServer: 注册 gRPC 服务的回调函数
func GrpcServerConnect(host string, port int, registerGrpcServer func(server *grpc.Server)) {
	grpcServerConnectHandle(host, port, registerGrpcServer, &base_config.RegisterConsul{})
}

// GrpcServerConnectRegisterConsul 启动 gRPC 服务器并注册到 Consul 服务中心
// 适用于微服务架构下，需要服务发现的场景
//
// 参数：
// - host: 监听的 IP 地址
// - port: 监听的端口号
// - registerGrpcServer: 注册 gRPC 服务的回调函数
// - registerConsul: Consul 配置信息（包含 Consul 连接信息和服务注册信息）
func GrpcServerConnectRegisterConsul(host string, port int, registerGrpcServer func(server *grpc.Server), registerConsul *base_config.RegisterConsul) {
	consul := registerConsul.Consul
	service := registerConsul.Register.Service

	// 检查 Consul 配置信息是否合法
	if consul.Host == "" || consul.Port == "" {
		panic("注册中心 Consul 配置错误：IP 地址和端口不能为空")
	}
	if service.Name == "" {
		panic("注册服务到 Consul 失败：服务名称不能为空")
	}
	if service.Address == "" {
		panic("注册服务到 Consul 失败：服务地址不能为空")
	}

	// 设置标识，表明该服务需要注册到 Consul
	registerConsul.IsRegister = true

	// 启动 gRPC 服务器并注册到 Consul
	grpcServerConnectHandle(host, port, registerGrpcServer, registerConsul)
}

// grpcConnectHandle 处理 gRPC 服务器启动和 Consul 服务注册
//
// 参数：
// - host: 监听的 IP 地址
// - port: 监听的端口号
// - registerGrpcServer: 注册 gRPC 服务的回调函数
// - registerConsul: Consul 配置信息（包含 Consul 连接信息和服务注册信息）
func grpcServerConnectHandle(host string, port int, registerGrpcServer func(server *grpc.Server), registerConsul *base_config.RegisterConsul) {
	// 定义命令行参数，用于指定 gRPC 服务器的 IP 地址和端口号
	//IP := flag.String("ip", "0.0.0.0", "gRPC 服务器监听的 IP 地址") // 默认监听所有 IP
	//Port := flag.Int("port", 50051, "gRPC 服务器监听的端口号")       // 默认端口号为 50051
	//
	//// 解析命令行参数
	//flag.Parse()
	//fmt.Println("gRPC 服务器启动，监听地址: ", *IP, "端口: ", *Port)

	// 创建 gRPC 服务器实例
	server := grpc.NewServer()

	// 注册 gRPC 服务（用户自定义的服务注册逻辑）
	registerGrpcServer(server)

	// 监听指定 IP 和端口
	lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port))
	if err != nil {
		// 监听失败，直接 退出
		//zap.S().Fatalf()（会直接 os.Exit(1)，直接退出, 适合初始化）
		zap.S().Fatalf("无法监听端口: %v", err)
	}

	//如果需要注册到 Consul，执行注册操作, 获取注册consul客户端以及服务ID
	client, serviceID := registerConsulClient(port, server, registerConsul)

	//goroutine 启动 gRPC 服务器，监听端口，处理请求，不进行阻塞主线程
	//让主线程继续执行=>监听退出信号注销consul服务功能
	startGrpcServer(server, lis)

	//处理服务退出时的注销逻辑（仅当服务已注册到 Consul 时）
	ConsulDeregister(serviceID, client)
}

// startGrpcServer 启动 gRPC 服务器并在独立的 goroutine 中监听请求。
// 此函数接受两个参数：
// - server：已创建并注册好服务的 gRPC 服务器实例。
// - lis：监听的 net.Listener，用于指定监听的地址和端口。
//
// 该函数会在一个新的 goroutine 中启动 gRPC 服务器，
// 这样不会阻塞主线程=>主线程可以继续执行其他任务（例如监听退出信号或启动其他服务), 从而实现并发处理请求。
func startGrpcServer(server *grpc.Server, lis net.Listener) {
	// 在一个新的 goroutine 中启动 gRPC 服务器，避免阻塞主线程
	go func() {
		// 调用 server.Serve(lis) 启动 gRPC 服务器，开始监听请求。
		// Serve 方法会阻塞当前 goroutine，直到服务器停止。
		err := server.Serve(lis)
		if err != nil {
			// 如果 gRPC 服务器启动失败，则打印错误信息并终止程序
			//panic("gRPC 服务器启动失败: " + err.Error())

			//zap.S().Fatalf()（会直接 os.Exit(1)，直接退出, 适合初始化）
			zap.S().Fatalf("gRPC 服务器启动失败: %v", err)
		}
	}()
}

func registerConsulClient(port int, server *grpc.Server, registerConsul *base_config.RegisterConsul) (*api.Client, string) {
	// 注册Consul 客户端
	if registerConsul.IsRegister {
		// 注册 gRPC 健康检查服务
		// 允许 Consul 通过 gRPC 的健康检查机制检测服务状态
		grpc_health_v1.RegisterHealthServer(server, health.NewServer())

		var registerService RegisterService = &GrpcRegisterService{
			Consul:   registerConsul.Consul,
			Register: &registerConsul.Register,
		}

		// 如果需要注册到 Consul，执行注册操作
		return registerService.ConsulRegister(port)
	}

	return nil, ""
}

type HttpRegisterService struct {
	Consul   base_config.ConsulConfig
	Register *base_config.Register
}
type GrpcRegisterService struct {
	Consul   base_config.ConsulConfig
	Register *base_config.Register
}

type RegisterService interface {
	ConsulRegister(port int) (*api.Client, string)
}

func (rs *HttpRegisterService) ConsulRegister(port int) (*api.Client, string) {
	return registerHandle(port, rs.Consul, rs.Register,
		func(register *base_config.Register) *api.AgentServiceRegistration {
			// 构造健康检查对象
			checkHealth := &api.AgentServiceCheck{
				HTTP:                           fmt.Sprintf("http://%s:%d/health", register.Service.Address, register.Service.Port), // gRPC 健康检查地址（格式：IP:Port）
				Timeout:                        register.Check.Timeout,                                                              // 健康检查超时时间
				Interval:                       register.Check.Interval,                                                             // 健康检查的间隔时间
				DeregisterCriticalServiceAfter: register.Check.DeregisterCritical,                                                   // 健康检查失败后，多久注销服务
			}
			return getRegistration(register.Service, checkHealth)
		})
}

func (rs *GrpcRegisterService) ConsulRegister(port int) (*api.Client, string) {
	return registerHandle(port, rs.Consul, rs.Register,
		func(register *base_config.Register) *api.AgentServiceRegistration {
			// 构造健康检查对象
			checkHealth := &api.AgentServiceCheck{
				GRPC:                           fmt.Sprintf("%s:%d", register.Service.Address, register.Service.Port), // gRPC 健康检查地址（格式：IP:Port）
				Timeout:                        register.Check.Timeout,                                                // 健康检查超时时间
				Interval:                       register.Check.Interval,                                               // 健康检查的间隔时间
				DeregisterCriticalServiceAfter: register.Check.DeregisterCritical,                                     // 健康检查失败后，多久注销服务
			}
			return getRegistration(register.Service, checkHealth)
		})
}

func registerHandle(port int, consul base_config.ConsulConfig, register *base_config.Register,
	serviceRegisterFunc func(*base_config.Register) *api.AgentServiceRegistration) (*api.Client, string) {

	// 生成唯一的服务 ID（格式：服务名称:UUID）
	serviceID := fmt.Sprintf("%s:%s", register.Service.Name, base_util.GenerateUUID())

	// 设置服务注册信息
	register.Service.ID = serviceID
	register.Service.Port = port

	registration := serviceRegisterFunc(register)

	zap.S().Infof("service[servicId:%s, addr:%s,port:%d], check[grpc:%s, http:%s]",
		serviceID, register.Service.Address, port, registration.Check.GRPC, registration.Check.HTTP)

	// 创建 Consul 客户端
	client := createConsulClient(consul)

	// 调用 Consul API 注册服务
	err := client.Agent().ServiceRegister(registration)
	if err != nil {
		// 注册失败，直接 panic 终止程序
		zap.S().Fatalf("服务注册失败: %v", err)
		//panic("服务注册失败: " + err.Error())
	}

	zap.S().Infof("服务成功注册到 Consul")

	return client, register.Service.ID
}

func getRegistration(service base_config.RegisterService, checkHealth *api.AgentServiceCheck) *api.AgentServiceRegistration {
	// 构造 Consul 服务注册对象
	registration := &api.AgentServiceRegistration{
		ID:      service.ID,      // 服务 ID，确保唯一性，避免冲突
		Name:    service.Name,    // 服务名称，在 Consul 中可见
		Port:    service.Port,    // 服务监听的端口
		Tags:    service.Tags,    // 服务标签，用于分类或过滤
		Address: service.Address, // 服务的 IP 地址
		Check:   checkHealth,     // 健康检查配置
	}
	return registration
}

func ConsulDeregister(serviceID string, client *api.Client) {
	// 处理服务退出时的注销逻辑（仅当服务已注册到 Consul 时）
	if client != nil {
		// 创建一个通道，用于监听系统信号
		quit := make(chan os.Signal)
		signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // 监听终止信号
		<-quit                                               // 阻塞等待信号

		// 收到终止信号后，注销 Consul 注册的服务
		if err := client.Agent().ServiceDeregister(serviceID); err != nil {
			zap.S().Error("注销失败: " + err.Error())
		} else {
			zap.S().Infof("服务 [%s] 注销成功!", serviceID)
		}
	}
}

func createConsulClient(consulConfig base_config.ConsulConfig) *api.Client {
	// 创建 Consul 配置
	// 如果 Consul 是集群部署（如使用负载均衡器 Nginx 或 HAProxy），
	// 需要将 `cfg.Address` 设置为负载均衡器的地址
	cfg := api.DefaultConfig()
	cfg.Address = fmt.Sprintf("%s:%s", consulConfig.Host, consulConfig.Port)

	// 创建 Consul 客户端
	client, err := api.NewClient(cfg)
	if err != nil {
		// 连接 Consul 失败，直接 panic 终止程序
		//zap.S().Fatalf()（会直接 os.Exit(1)，直接退出, 适合初始化）
		zap.S().Fatalf("连接 Consul 注册中心失败: %v", err)
		//panic("连接 Consul 注册中心失败: " + err.Error())
	}

	//zap.S().Fatalf 不会执行返回client, 会直接 os.Exit(1)，直接退出
	return client
}
